package net.sudot.chess.business.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import net.sudot.chess.base.Chess;
import net.sudot.chess.config.BaseEntityAuditing;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
import java.io.Serializable;
import java.time.Instant;

/**
 * 基础实体类
 * <p></p>
 * 所有继承该实体类的实体在映射成表的时候会自动加入该类中的字段
 *
 * <a href="https://github.com/communicode/communikey-backend/commit/82dd196629767de53861c994fee558447b248dee">
 * <h2>Use "Instant" class instead of "ZonedDateTime" or "OffsetDateTime" for auditing</h2>
 * </a>
 * 译文:<br/>
 * 从Spring Boot 2.0开始"java.time.ZonedDateTime"类不支持直接使用JPA审计(AuditAware)特性,若要使用,需注册一个DateTimeProvider的Bean.详见Issues DATAJPA-1242 (1) 和 GH-10743 (2).
 * <br/><br/>
 * 不建议使用"ZonedDateTime"来实现审计,因为时区是属于显示层面在意的事情.使用"LocalDateTime"也是有争议的.它不代表一个时间点，仅代表一个时区.当前spring中"CurrentDateTimeProvider"的实现是使用JVM的默认时区.如果应用程序在跨时区的集群中运行，则会导致值是“LocalDateTime”,而这是无法比较的.
 * <br/><br/>
 * 推荐使用类"java.time.Instant"代替,因为不涉及时区,却描述了一个时间点.详见issue DATACMNS-1243 (3) 为"Instant"和"LocalDateTime"的相互转换添加了转换器.
 * <br/><br/>
 * <p>
 * 原文:<br/>
 * As of Spring Boot 2.0 the "java.time.ZonedDateTime" class is not supported for JPA auditing (AuditAware) out-of-the-box and required to register a DateTimeProvider bean. See reported issues DATAJPA-1242 (1) and GH-10743 (2).
 * <br/><br/>
 * The usage of "ZonedDateTime" for auditing has been advised against because the time zone is really more of a presentation layer concern. The use of "LocalDateTime" is problematic as well. It does not represent a point of time but does so only together with a timezone. The current implementation of Spring "CurrentDateTimeProvider" uses the JVM default timezone. If an application runs in a cluster across time zones this results in values of "LocalDateTime" values that can't be compared anymore.
 * <br/><br/>
 * The recommended class to use is "java.time.Instant" instead because there is no timezone involved, yet it specifies a point in time. For details see issue DATACMNS-1243 (3) which added converters for "Instant" <-> "LocalDateTime".
 * <br/><br/>
 * References:
 * <br/><br/>
 *
 * <a href="https://jira.spring.io/browse/DATAJPA-1242">https://jira.spring.io/browse/DATAJPA-1242</a><br/>
 * <a href="https://github.com/spring-projects/spring-boot/issues/10743">https://github.com/spring-projects/spring-boot/issues/10743</a><br/>
 * <a href="https://jira.spring.io/browse/DATACMNS-1243">https://jira.spring.io/browse/DATACMNS-1243</a><br/>
 *
 * @author tangjialin on 2018-06-11.
 */
@Getter
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@SuppressWarnings("unchecked")
public abstract class BaseEntity<E extends BaseEntity> extends Chess implements Serializable {
    private static final long serialVersionUID = -1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @JsonIgnore
    @Version
    private Integer version;
    /** 记录是否逻辑删除 。true 已删除，false 未删除 */
    private Boolean deleted;
    /** 创建时间 */
    @CreatedDate
    private Instant createdDate;
    /**
     * 创建人
     *
     * @see BaseEntityAuditing
     */
    @CreatedBy
    private Long createdBy;
    /** 修改时间 */
    @LastModifiedDate
    private Instant lastModifiedDate;
    /**
     * 修改人
     *
     * @see BaseEntityAuditing
     */
    @LastModifiedBy
    private Long lastModifiedBy;

    public E setId(Long id) {
        this.id = id;
        return (E) this;
    }

    public E setVersion(Integer version) {
        this.version = version;
        return (E) this;
    }

    public E setDeleted(Boolean deleted) {
        this.deleted = deleted;
        return (E) this;
    }

    public E setCreatedDate(Instant createdDate) {
        this.createdDate = createdDate;
        return (E) this;
    }

    public E setCreatedBy(Long createdBy) {
        this.createdBy = createdBy;
        return (E) this;
    }

    public E setLastModifiedDate(Instant lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
        return (E) this;
    }

    public E setLastModifiedBy(Long lastModifiedBy) {
        this.lastModifiedBy = lastModifiedBy;
        return (E) this;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) { return true; }
        if (obj == null) { return false; }
        if (obj.getClass() != this.getClass()) { return false; }

        Long eId = ((BaseEntity) obj).getId();
        return null != id && id.equals(eId);
    }

    @Override
    public int hashCode() {
        return toString().hashCode();
    }

    @Override
    public String toString() {
        return this.getClass().getName() + ":" + id;
    }
}
